package cn.yx.common.core.util.reflect;

import cn.hutool.core.collection.CollectionUtil;
import cn.yx.common.core.constant.message.reflect.ReflectUtilMessageKey;
import cn.yx.common.core.exception.CommonException;
import cn.yx.common.core.util.bean.PropertyUtil;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * <p>反射工具类</p>
 *
 * @author Wgssmart
 */
@Slf4j
public final class ReflectUtil {

    private ReflectUtil() {
    }

    /**
     * 判断指定对象是否有指定的属性
     *
     * @param property
     * @return
     */
    public static boolean hasProperty(Object obj, String property) {
        return hasProperty(obj.getClass(), property);
    }

    /**
     * 判断指定类是否有指定的属性
     *
     * @param clazz
     * @param property
     * @return
     */
    public static boolean hasProperty(Class<?> clazz, String property) {
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            return field.getName().equals(property);
        }
        return false;
    }

    /**
     * 判断指定对象是否有指定的多个属性
     *
     * @param obj
     * @param properties
     * @return
     */
    public static boolean hasProperties(Object obj, String... properties) {
        return hasProperties(obj.getClass(), properties);
    }

    /**
     * 判断指定类是否有指定的多个属性
     *
     * @param clazz
     * @param properties
     * @return
     */
    public static boolean hasProperties(Class<?> clazz, String... properties) {
        return hasProperties(clazz, Arrays.stream(properties).distinct().collect(Collectors.toList()));
    }

    /**
     * 判断指定对象是否有指定的多个属性
     *
     * @param obj
     * @param properties
     * @return
     */
    public static boolean hasProperties(Object obj, List<String> properties) {
        return hasProperties(obj.getClass(), properties);
    }

    /**
     * 判断指定类是否有指定的多个属性
     *
     * @param clazz
     * @param properties
     * @return
     */
    public static boolean hasProperties(Class<?> clazz, List<String> properties) {
        Field[] fields = clazz.getDeclaredFields();
        int hasPropertyCount = 0;
        for (Field field : fields) {
            if (properties.contains(field.getName())) {
                hasPropertyCount += 1;
            }
        }
        return hasPropertyCount == properties.size();
    }

    /**
     * 调用指定属性的getter方法
     *
     * @param obj      对象
     * @param property 属性名
     * @return getter方法的返回值
     */
    public static Object invokeGetter(Object obj, String property) {
        return invokeGetter(obj.getClass(), obj, property);
    }

    /**
     * 调用指定属性的getter方法
     *
     * @param obj      对象
     * @param property 属性名
     * @return getter方法的返回值
     */
    public static <T> T invokeGetter(Object obj, String property, Class<T> toType) {
        return invokeGetter(obj.getClass(), obj, property, toType);
    }

    /**
     * 调用指定类对象指定属性的getter方法
     *
     * @param clazz    类
     * @param obj      对象
     * @param property 属性名
     * @return
     */
    public static Object invokeGetter(Class<?> clazz, Object obj, String property) {
        try {
            Method method = clazz.getMethod(PropertyUtil.getter(property));
            return method.invoke(obj);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            log.error("invoke getter error: {}, class: {}, property: {}", e.getMessage(), clazz.getName(), property);
            throw new CommonException(ReflectUtilMessageKey.INVOKE_GETTER_ERROR, e);
        }
    }

    /**
     * 调用指定类对象指定属性的getter方法
     *
     * @param clazz    类
     * @param obj      对象
     * @param property 属性名
     * @return
     */
    public static <T> T invokeGetter(Class<?> clazz, Object obj, String property, Class<T> toType) {
        try {
            Method method = clazz.getMethod(PropertyUtil.getter(property));
            return toType.cast(method.invoke(obj));
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            log.error("invoke getter error: {}, class: {}, property: {}", e.getMessage(), clazz.getName(), property);
            throw new CommonException(ReflectUtilMessageKey.INVOKE_GETTER_ERROR, e);
        }
    }

    /**
     * 调用指定属性的setter方法
     *
     * @param obj      对象
     * @param property 属性名
     * @param param    setter方法的参数
     */
    public static void invokeSetter(Object obj, String property, Object param) {
        invokeSetter(obj.getClass(), obj, property, param);
    }

    /**
     * 调用指定类对象的指定属性的setter方法
     *
     * @param clazz    类
     * @param obj      对象
     * @param property 属性名
     * @param param    参数值
     */
    public static void invokeSetter(Class<?> clazz, Object obj, String property, Object param) {
        if (Objects.nonNull(param)) {
            try {
                Method method = clazz.getMethod(PropertyUtil.setter(property), param.getClass());
                method.invoke(obj, param);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                log.error("invoke setter error: {}, class: {}, property: {}, param: {}",
                        e.getMessage(), clazz.getName(), property, param);
                throw new CommonException(ReflectUtilMessageKey.INVOKE_SETTER_ERROR, e);
            }
        }
    }

    /**
     * 调用多个属性的setter方法
     *
     * @param obj              对象
     * @param propertyParamMap 属性名和对应参数值的Map对象
     */
    public static void invokeSetters(Object obj, Map<String, Object> propertyParamMap) {
        invokeSetters(obj.getClass(), obj, propertyParamMap);
    }

    /**
     * 调用指定类对象的多个属性的setter方法
     *
     * @param clazz            类
     * @param obj              对象
     * @param propertyParamMap 属性名和对应参数值的Map对象
     */
    public static void invokeSetters(Class<?> clazz, Object obj, Map<String, Object> propertyParamMap) {
        if (CollectionUtil.isNotEmpty(propertyParamMap)) {
            for (Map.Entry<String, Object> propertyParamEntry : propertyParamMap.entrySet()) {
                String property = propertyParamEntry.getKey();
                Object param = propertyParamEntry.getValue();
                invokeSetter(clazz, obj, property, param);
            }
        }
    }

    /**
     * 反射获取指定对象的属性值
     *
     * @param obj      指定的对象
     * @param property 指定的属性
     * @return 属性值
     */
    public static Object getPropertyValue(Object obj, String property) {
        return getPropertyValue(obj.getClass(), obj, property);
    }

    /**
     * 反射获取指定对象的属性值
     *
     * @param obj      指定的对象
     * @param property 指定的属性
     * @param toType   属性值的类型
     * @return 属性值
     */
    public static <T> T getPropertyValue(Object obj, String property, Class<T> toType) {
        return getPropertyValue(obj.getClass(), obj, property, toType);
    }

    /**
     * 反射获取指定类对象的属性值
     *
     * @param clazz    类
     * @param obj      指定的对象
     * @param property 指定的属性
     * @return 属性值
     */
    public static Object getPropertyValue(Class<?> clazz, Object obj, String property) {
        try {
            Field field = clazz.getDeclaredField(property);
            field.setAccessible(true);
            return field.get(obj);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            log.error("get property value error: {}, class: {}, property: {}", e.getMessage(), clazz.getName(), property);
            throw new CommonException(ReflectUtilMessageKey.GET_PROPERTY_VALUE_ERROR, e);
        }
    }

    /**
     * 反射获取指定类对象的属性值
     *
     * @param clazz    类
     * @param obj      指定的对象
     * @param property 指定的属性
     * @param toType   属性值的类型
     * @return 属性值
     */
    public static <T> T getPropertyValue(Class<?> clazz, Object obj, String property, Class<T> toType) {
        return toType.cast(getPropertyValue(clazz, obj, property));
    }

    /**
     * 反射设置指定对象的属性
     *
     * @param obj      指定的对象
     * @param property 指定的属性
     * @param param    属性需要设置的值
     */
    public static void setPropertyValue(Object obj, String property, Object param) {
        setPropertyValue(obj.getClass(), obj, property, param);
    }

    /**
     * 反射设置指定类对象的属性
     *
     * @param clazz    类
     * @param obj      指定的对象
     * @param property 指定的属性
     * @param param    属性需要设置的值
     */
    public static void setPropertyValue(Class<?> clazz, Object obj, String property, Object param) {
        try {
            Field field = clazz.getDeclaredField(property);
            field.setAccessible(true);
            field.set(obj, param);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            log.error("set property value error: {}, class: {}, property: {}, param: {}",
                    e.getMessage(), clazz.getName(), property, param);
            throw new CommonException(ReflectUtilMessageKey.SET_PROPERTY_VALUE_ERROR, e);
        }
    }

    /**
     * 反射设置指定对象的属性
     *
     * @param obj              指定的对象
     * @param propertyParamMap 属性名和对应参数值的Map对象
     */
    public static void setPropertyValues(Object obj, Map<String, Object> propertyParamMap) {
        setPropertyValues(obj.getClass(), obj, propertyParamMap);
    }

    /**
     * 反射设置指定类对象的属性
     *
     * @param clazz            类
     * @param obj              指定的对象
     * @param propertyParamMap 属性名和对应参数值的Map对象
     */
    public static void setPropertyValues(Class<?> clazz, Object obj, Map<String, Object> propertyParamMap) {
        if (CollectionUtil.isNotEmpty(propertyParamMap)) {
            for (Map.Entry<String, Object> propertyParamEntry : propertyParamMap.entrySet()) {
                String property = propertyParamEntry.getKey();
                Object param = propertyParamEntry.getValue();
                setPropertyValue(clazz, obj, property, param);
            }
        }
    }

    /**
     * 获取指定类、方法名和参数类型的所有参数名
     *
     * @param clazz
     * @param methodName
     * @param parameterTypes
     * @return 如果无参数，返回null
     */
    public static String[] getParamNames(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
        try {
            Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
            return getParamNames(method);
        } catch (NoSuchMethodException e) {
            log.error("get param names error: {}, class: {}, method: {}", e.getMessage(), clazz.getName(), methodName);
            throw new CommonException(ReflectUtilMessageKey.GET_PARAM_NAMES_ERROR, e);
        }
    }

    /**
     * 获取指定类中指定方法名的所有参数名称
     *
     * @param clazz
     * @param methodName
     * @return 如果无参数，返回null
     */
    public static String[] getParamNames(Class<?> clazz, String methodName) {
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                return getParamNames(method);
            }
        }
        return null;
    }

    /**
     * 通过方法名获取参数名列表
     *
     * @param method
     * @return
     */
    private static String[] getParamNames(Method method) {
        Parameter[] parameters = method.getParameters();
        int parameterCount = parameters.length;
        if (parameterCount == 0) {
            return null;
        }
        String[] argsNames = new String[parameterCount];
        for (int i = 0; i < parameterCount; i++) {
            argsNames[i] = parameters[i].getName();
        }
        return argsNames;
    }

}